# 
# SQLDoc.rb
# 
# Created on Mar 17, 2008, 2:47:02 PM
# 
# Author: Waigo

# Dealing with prefix of table/sp/function/view
# EBIZ1, SODS2, WWPP2, 
# S_, D_, U_, I_, IU_, 
# F_, V_, 
# SHAR2_

=begin
-------------------------------------------------------------------------------
--  MAINTENANCE LOG
--  who  date        comment
--  ---  --------    ---------------------------------------------------------------
--  Waigo 03/17/08   Initial Version
--  Waigo            ... upgraded and fixed bug
--  Waigo 04/11/08   - Added Ruby UnitTest support. test_main, test_relation_map.
                     - Added multi directory support.
                     - Added relative object list.
                     - Added generate date on view, changed \t to 4 spaces in HTML;
--  Waigo 04/15/08   - Add Quick input finder in left.html
--  Waigo 04/29/08   - Parsed link <a> for database obj in source code
--  Waigo 06/27/08   - Optimized code and combined main.rb.
------------------------------------------------------------------------------------
=end


require 'erb'

module SQLDocFunc
  def gen_url(item, schema_flag = true)
    s_name = item.to_sname
	name = schema_flag? item : s_name         
    if @gbl_index.index(s_name)
      "<a href=\"#{s_name}.html\">#{name}</a>"
    else
      item
    end    
  end  
  
  def to_sname(item)
	# Remove the schema or file extern name. i.e: .SQL, EBIZ1., etc.
    item.gsub(/\.SQL/i, '').gsub(/(\w+\.)/i, '').upcase
  end
end

class SQLDoc
  include SQLDocFunc
  attr_accessor :input_dir, :output_dir 
  attr_accessor :gbl_index, :relation_map
  attr_reader :input_dir_arr

  def initialize(input_dir, output_dir)
    return if input_dir.nil? || output_dir.nil?  
    return if output_dir.split(',').size>1     # Only one output directory.
    @input_dir_arr = input_dir.split(',')      # Multi input directories, split by commas
    @output_dir = output_dir
    @relation_map = Hash.new()
    @gbl_index = []
  end
  
  def run
    # Collect files into @gbl_index and @relation_map
    files = []    
    @input_dir_arr.each do |d|
      d = d.strip
      unless d.empty?
        @input_dir = d
        files = files.concat( get_all_files(self.input_dir) )
      end
    end    
    @gbl_index.uniq!
    files.uniq!
    @relation_map.each do |key, arr|
      arr_clean(arr, key)
    end    
    @relation_map.delete_if {|key, value| value.empty? }
    
    # Process each file to generate html 
    puts "Generating each script file..." + "Total: "
    process(files) 
    
    # Generate index file
    puts "Generating index file..."
    gen_index()
    puts "-------------------------------------------"
    puts "Generate SP Doc Completed!!"

  end
  
  
  def parse_item(item)
    return if item.nil? || item.empty?    
    item = item.upcase
    case item
      when /\.S_|\.D_|\.U_|\.I_|\.IU_/
        @arr_sp << item
      when /\.F_/
        @arr_fun << item
      when /\.V_/
        @arr_view << item
      when /EBIZ1\.|SODS2\.|WWPP2\.|SHAR2\./
        @arr_table << item
    end
  end
  
  # Support templating of member data.
  def get_binding
    binding
  end

  def arr_clean(arr, base_name)
    # Remove duplicate values and itself base_name in array 
    arr.uniq!
    arr.delete_if {|item| item =~ /\.#{base_name}/i}
  end

  def puts_all(base_name)
    arr_clean(@arr_table, base_name)
    arr_clean(@arr_sp, base_name)
    arr_clean(@arr_fun, base_name)
    arr_clean(@arr_view, base_name)    
    
    puts "--------Resolving #{@script_name}:------------"
    
    unless @arr_table.nil?
      puts "----------------Table:"
      @arr_table.each{|item| puts item}
    end    
    unless @arr_sp.nil?
      puts "----------------Store procedure:"
      @arr_sp.each{|item| puts item}
    end
    unless @arr_fun.nil?
      puts "----------------Functions:"
      @arr_fun.each{|item| puts item}  
    end
    unless @arr_view.nil?
      puts "----------------Views:"
      @arr_view.each{|item| puts item}        
    end  
  end

  def gen_html(file)
    return if file[/\w+.sql$/i].nil?          # If it's not a .sql file. 
    base_name = file[/\w+.sql$/i].to_sname
    @arr_table, @arr_sp, @arr_fun, @arr_view = [], [], [], []
    @arr_rel_obj = []
    @arr_rel_obj = @relation_map[base_name]
    @script_name = base_name
        
    fo = File.open(file)
    content = fo.readlines.to_s   
       
    @author = content.scan(/\s*--\s*Author:\s*(.*)/i)
    @date = content.scan(/\s*--\s*Date:\s*(.*)/i)
    @abstract = content.scan(/Abstract:(.*)Sample Calls/im).to_s.gsub(/--/, '').gsub(/^\n/, '')
    @samplecalls = content.scan(/Sample Calls:(.*)Maintenance/im).to_s.gsub(/--/, '').gsub(/^\n/, '').gsub(/\n/, '<br>')
    
    pure_code = content.gsub(/\s*--.*/, '')          # Clean the comments
    
    pure_code.scan(/\w+\.\w+/i) do |item|
      parse_item(item)
    end
    
    # Parsed link <a> for database obj in source code
    @arr_all = @arr_table + @arr_sp + @arr_fun + @arr_view
    @arr_all.uniq!
    @arr_all.sort!{|a, b| a.length <=> b.length}   # Sort by length asc
    @arr_all.each do |item|
      s_name = item.to_sname
      if base_name != s_name && @gbl_index.index(s_name)
        content.gsub!(/(#{Regexp.escape(item)})(\W|\n)/i, item.to_url + '\2')        
      end
    end
    
    content = SQLHighlighter.cover(content)
    @src_code = content

    puts_all(base_name)
    
    rhtml = ERB.new(File.open(HOME_DIR + "/templates/item.rhtml").readlines.to_s)        
    file = File.open("#{@output_dir}/#{base_name}.html", 'w+')
    file.puts(rhtml.result(self.get_binding))
  end
  
  def process(f_items)
    return if f_items.nil?
    f_items.each do |fn| 
      if fn.class == Hash    
        fn.each do |k, v| 
          #unless File.directory?(k) && File.exists?(k)
            #Dir.mkdir(k)
          #end
          
          #in_path = in_path.to_s + "\\" + k.to_s        
          # Put the output files in the same dir
          # output_dir = output_dir.to_s + "\\" + k.to_s
#          unless File.exists?(k)
#            Dir.mkdir(output_dir)
#          end 
          process(v) 
          #in_path.gsub!(/\\#{k}$/, '')
          #out_path.gsub!(/\\#{k}$/, '')
        end
      elsif fn =~ /\.SQL$/i
        gen_html(fn)      
      end
    end
  end
  
  def gen_index()
    require 'ftools'
    
    @arr_index = @gbl_index
    File.copy(HOME_DIR + "/templates/style.css", @output_dir + "/style.css")
    File.copy(HOME_DIR + "/templates/index.html", @output_dir + "/index.html")    
    rhtml = ERB.new(File.open(HOME_DIR + '/templates/left.rhtml').readlines.to_s)    
    file = File.open("#{@output_dir}/left.html", 'w+')
    file.puts(rhtml.result(binding))
    
    # system("start \"" + output_dir + "\"/index.html")    
  end
  
  
  def get_all_files(path)   
    re = []   
    Dir.foreach(path) do |f|  
       fullfilename = path + "/" + f
       if f == "." or f == ".."    
       elsif File.directory?(fullfilename)   
          resub = []   
          resub = get_all_files(fullfilename)
          if resub.length > 0   
              ref = {}
              ref[f] = resub
              re << ref   
          end  
        elsif File.exist?(fullfilename) and (f =~ /\.SQL$/i) # only rb file   
          re << fullfilename   
          collect_all_items(fullfilename)
        end  
    end  
    return re   
  end 
  
  def collect_all_items(fullfilename)
    base_name = fullfilename[/\w+.sql$/i].to_sname
    
    file = File.open(fullfilename)
    content = file.readlines.to_s    
    pure_code = content.gsub(/\s*--.*/, '')          # Clean the comments
    
    # Find out the item name with schema. Just pending
    # specific_name = content.scan(/SPECIFIC\s*(\w+\.\w+)/i).slice(0).to_s
    # specific_name = base_name if specific_name.nil? || specific_name==''
    # specific_name.upcase!
    
    specific_name = base_name

    @gbl_index << specific_name
    pure_code.scan(/\w+\.\w+/) do |item|
      if item =~ /\.S_|\.D_|\.U_|\.I_|\.IU_|\.F_|\.V_|EBIZ1\.|SODS2\.|WWPP2\.|SHAR2\./i
        item = item.to_sname
        if item != specific_name          
          @relation_map[item] = [] if @relation_map[item].nil?
          @relation_map[item] << specific_name       
        end              
      end           
   end
 end
  
end


class SQLHighlighter
  def self.pattern
    [
	[/\'(.*?)(\')/i, '<s>\'\1\2</s>'], 
	[/\b(add|after|aggregate|alias|all|and|as|authorization|between|by|cascade|cache|cache|called|case|check|column|comment|constraint|createdb|createuser|cycle|database|default|deferrable|deferred|diagnostics|distinct|domain|each|else|elseif|elsif|encrypted|except|exception|for|foreign|from|from|full|function|get|group|having|if|immediate|immutable|in|increment|initially|increment|index|inherits|inner|input|intersect|into|invoker|is|join|key|language|left|like|limit|local|loop|match|maxvalue|minvalue|natural|nextval|no|nocreatedb|nocreateuser|not|null|of|offset|oids|on|only|operator|or|order|outer|owner|partial|password|perform|plpgsql|primary|record|references|replace|restrict|return|returns|right|row|rule|schema|security|sequence|session|sql|stable|statistics|table|temp|temporary|then|time|to|transaction|trigger|type|unencrypted|union|unique|user|using|valid|value|values|view|volatile|when|where|with|without|zone)\b/i, '<b>\1</b>'],
	[/\b(bigint|bigserial|bit|boolean|box|bytea|char|character|cidr|circle|date|decimal|double|float4|float8|inet|int2|int4|int8|integer|interval|line|lseg|macaddr|money|numeric|oid|path|point|polygon|precision|real|refcursor|serial|serial4|serial8|smallint|text|timestamp|varbit|varchar)\b/i, '<u>\1</u>'], 
	[/\b(abort|alter|analyze|begin|checkpoint|close|cluster|comment|commit|copy|create|deallocate|declare|delete|drop|end|execute|explain|fetch|grant|insert|listen|load|lock|move|notify|prepare|reindex|reset|restart|revoke|rollback|select|set|show|start|truncate|unlisten|update)\b/i, '<em>\1</em>'],
	[/(.*?)--(.*?)(\n|<br|<\/P)/i, '\1<i>--\2</i>\3'], #comments
        [/\t/, '&nbsp;&nbsp;&nbsp;&nbsp;'], 
        [/ /, '&nbsp;'], 
        [/\n/, '<br />']        
    ]
  end
  def self.cover(content) 
    pattern.each do |regx, repl|
      content.gsub!(regx, repl)
    end
    content
  end
end

class String
  def to_sname()
    self.gsub(/\.SQL/i, '').gsub(/(\w+\.)/i, '').upcase
  end
  
  def to_url()
    s_name = self.to_sname    
    self.gsub(self, '<a href=' + s_name + '.html>' + self + '</a>')
  end
end



# main

HOME_DIR = File.dirname(__FILE__)

require 'yaml'

config = YAML.load(File.open(HOME_DIR + '/config.yaml'))
input_dir = config['input.dir'] 
output_dir = config['output.dir'] 

doc = SQLDoc.new(input_dir, output_dir)
doc.run









